﻿using System;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 时间戳工厂类
    /// </summary>
    public class TimestampFactory : ITimestampFactory
    {
        /// <summary>
        /// 参照基准时间
        /// </summary>
        private readonly DateTime _refTime;

        /// <summary>
        /// 参考时间最大值
        /// </summary>
        private readonly DateTime _refTimeMax;

        /// <summary>
        /// 时间戳单位
        /// </summary>
        private readonly TimestampUnit _timestampUnit;

        /// <summary>
        /// 时间戳精度
        /// </summary>
        private readonly uint _precision;

        /// <summary>
        /// TimeSpan.TotalXX值与目标单位之间转换倍数
        /// </summary>
        private readonly uint _convertMuilt;

        /// <summary>
        /// 默认时间戳值是否需要再次进行转换[true:需要;false:不需要]
        /// </summary>
        private readonly bool _hasConvert = false;

        /// <summary>
        /// 参照基准时间对应的时间戳值,即当前支持的最小时间戳
        /// </summary>
        private readonly double _minTimestamp;

        /// <summary>
        /// 参考时间最大值对应的时间戳值,即当前支持的最大时间戳
        /// </summary>
        private readonly double _maxTimestamp;



        #region 属性
        /// <summary>
        /// 获取参照基准时间
        /// </summary>
        public DateTime RefTime
        {
            get { return this._refTime; }
        }

        /// <summary>
        /// 获取参考时间最大值
        /// </summary>
        public DateTime RefTimeMax
        {
            get { return this._refTimeMax; }
        }

        /// <summary>
        /// 获取时间戳单位
        /// </summary>
        public TimestampUnit TimestampUnit
        {
            get { return this._timestampUnit; }
        }

        /// <summary>
        /// 获取时间戳精度
        /// </summary>
        public uint Precision
        {
            get { return this._precision; }
        }

        /// <summary>
        /// 获取参照基准时间对应的时间戳值,即当前支持的最小时间戳
        /// </summary>
        public double MinTimestamp
        {
            get { return this._minTimestamp; }
        }

        /// <summary>
        /// 获取参考时间最大值对应的时间戳值,即当前支持的最大时间戳
        /// </summary>
        public double MaxTimestamp
        {
            get { return this._maxTimestamp; }
        }
        #endregion




        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="refTime">参考时间</param>
        /// <param name="timestampUnit">时间戳单位,默认毫秒</param>
        /// <param name="precision">时间戳精度</param>
        public TimestampFactory(DateTime refTime, TimestampUnit timestampUnit = TimestampUnit.Millisecond, uint precision = 1)
            : base()
        {
            this._refTime = refTime;
            this._refTimeMax = new DateTime(9999, 12, 31, 23, 59, 59, refTime.Kind);
            this._timestampUnit = timestampUnit;
            this._precision = precision;

            uint convertMuilt;
            switch (timestampUnit)
            {
                case TimestampUnit.Day:
                case TimestampUnit.Hour:
                case TimestampUnit.Minute:
                case TimestampUnit.Second:
                case TimestampUnit.Millisecond:
                    convertMuilt = 1;
                    break;
                case TimestampUnit.Microsecond:
                    convertMuilt = 1000;
                    break;
                case TimestampUnit.Nanosecond100:
                    convertMuilt = 10000;
                    break;
                case TimestampUnit.Nanosecond:
                    convertMuilt = 1000000;
                    break;
                case TimestampUnit.Picosecond:
                    convertMuilt = 1000000000;
                    break;
                default:
                    throw new NotImplementedException(this._timestampUnit.ToString());
            }
            this._convertMuilt = convertMuilt;

            this._hasConvert = convertMuilt != 1 || precision != 1;

            this._minTimestamp = this.PrimitiveGetTimestamp(refTime.Kind, this._refTime);
            this._maxTimestamp = this.PrimitiveGetTimestamp(refTime.Kind, this._refTimeMax);

        }




        #region Timestamp

        /// <summary>
        /// 获取指定日期时间的时间戳
        /// </summary>
        /// <param name="kind">时间戳类型</param>
        /// <param name="dateTime">计算时间戳时间</param>
        /// <returns>当前时间的时间戳</returns>
        private long PrimitiveGetTimestamp(DateTimeKind kind, DateTime dateTime)
        {
            if (dateTime.Kind != kind)
            {
                dateTime = TimeEx.ConvertDateTimeKind(dateTime, kind);
            }

            TimeSpan ts = dateTime - this._refTime;
            double timestamp;
            switch (this._timestampUnit)
            {
                case TimestampUnit.Day:
                    timestamp = ts.TotalDays;
                    break;
                case TimestampUnit.Hour:
                    timestamp = ts.TotalHours;
                    break;
                case TimestampUnit.Minute:
                    timestamp = ts.TotalMinutes;
                    break;
                case TimestampUnit.Second:
                    timestamp = ts.TotalSeconds;
                    break;
                case TimestampUnit.Millisecond:
                case TimestampUnit.Microsecond:
                case TimestampUnit.Nanosecond100:
                case TimestampUnit.Nanosecond:
                case TimestampUnit.Picosecond:
                    timestamp = ts.TotalMilliseconds;
                    break;
                default:
                    throw new NotImplementedException(this._timestampUnit.ToString());
            }

            if (this._hasConvert)
            {
                timestamp = timestamp * this._convertMuilt / this._precision;
            }

            return (long)timestamp;
        }

        /// <summary>
        /// 获取当前日期时间的时间戳
        /// </summary>
        /// <returns>当前时间的时间戳</returns>
        public long GetTimestamp()
        {
            return this.PrimitiveGetTimestamp(this._refTime.Kind, TimeEx.GetNow(this._refTime.Kind));
        }

        /// <summary>
        /// 获取指定日期时间的时间戳
        /// </summary>
        /// <param name="dateTime">计算时间戳时间</param>
        /// <returns>当前时间的时间戳</returns>
        public long GetTimestamp(DateTime dateTime)
        {
            return this.PrimitiveGetTimestamp(this._refTime.Kind, dateTime);
        }

        /// <summary>
        /// 时间戳转换为DateTime
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns>时间</returns>
        public DateTime TimestampToDateTime(long timestamp)
        {
            return this.TimestampToDateTime((double)timestamp);
        }

        /// <summary>
        /// 时间戳转换为DateTime
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns>时间</returns>
        public DateTime TimestampToDateTime(double timestamp)
        {
            if (this._hasConvert)
            {
                timestamp = timestamp * this._precision / this._convertMuilt;
            }

            DateTime time;
            if (timestamp <= this._minTimestamp)
            {
                time = this._refTime;
            }
            else if (timestamp >= this._maxTimestamp)
            {
                time = this._refTimeMax;
            }
            else
            {
                switch (this._timestampUnit)
                {
                    case TimestampUnit.Day:
                        time = this._refTime.AddDays(timestamp);
                        break;
                    case TimestampUnit.Hour:
                        time = this._refTime.AddHours(timestamp);
                        break;
                    case TimestampUnit.Minute:
                        time = this._refTime.AddMinutes(timestamp);
                        break;
                    case TimestampUnit.Second:
                        time = this._refTime.AddSeconds(timestamp);
                        break;
                    case TimestampUnit.Millisecond:
                    case TimestampUnit.Microsecond:
                    case TimestampUnit.Nanosecond100:
                    case TimestampUnit.Nanosecond:
                    case TimestampUnit.Picosecond:
                        time = this._refTime.AddMilliseconds(timestamp);
                        break;
                    default:
                        throw new NotImplementedException(this._timestampUnit.ToString());
                }
            }

            return time;
        }

        #endregion


        /*
        #region TicksTimestamp
        /// <summary>
        /// 获取指定时间的Ticks时间戳
        /// </summary>
        /// <param name="dateTime">时间</param>
        /// <param name="timestampUnit">Ticks时间戳单位</param>
        /// <param name="precision">时间戳精度</param>
        /// <returns>Ticks时间戳</returns>
        public double GetTicksTimestampD(DateTime dateTime, TimestampUnit timestampUnit = TimestampUnit.Millisecond, uint precision = 1)
        {
            double timestamp;
            switch (timestampUnit)
            {
                case TimestampUnit.Day:
                case TimestampUnit.Hour:
                case TimestampUnit.Minute:
                case TimestampUnit.Second:
                    TimeSpan ts = dateTime - this._refTime;
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Day:
                            timestamp = ts.TotalDays;
                            break;
                        case TimestampUnit.Hour:
                            timestamp = ts.TotalHours;
                            break;
                        case TimestampUnit.Minute:
                            timestamp = ts.TotalMinutes;
                            break;
                        case TimestampUnit.Second:
                            timestamp = ts.TotalSeconds;
                            break;
                        default:
                            throw new NotImplementedException(timestampUnit.ToString());
                    }

                    break;
                case TimestampUnit.Millisecond:
                case TimestampUnit.Microsecond:
                case TimestampUnit.Nanosecond:
                case TimestampUnit.Picosecond:
                    double microseconds = (double)dateTime.Ticks * 100;
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Millisecond:
                            timestamp = microseconds / 1e6;
                            break;
                        case TimestampUnit.Microsecond:
                            timestamp = microseconds / 1e3;
                            break;
                        case TimestampUnit.Nanosecond:
                            timestamp = microseconds;
                            break;
                        case TimestampUnit.Picosecond:
                            timestamp = microseconds * 1000;
                            break;
                        default:
                            throw new NotImplementedException(timestampUnit.ToString());
                    }
                    break;
                default:
                    throw new NotImplementedException(timestampUnit.ToString());
            }

            if (precision > 1)
            {
                timestamp = timestamp / precision;
            }

            return timestamp;
        }

        /// <summary>
        /// 获取指定时间的Ticks时间戳
        /// </summary>
        /// <param name="timestampUnit">Ticks时间戳单位</param>
        /// <param name="precision">时间戳精度</param>
        /// <returns>Ticks时间戳</returns>
        public double GetTicksTimestampD(TimestampUnit timestampUnit = TimestampUnit.Millisecond, uint precision = 1)
        {
            return this.GetTicksTimestampD(TimeEx.GetNow(this._refTime.Kind), timestampUnit, precision);
        }


        /// <summary>
        /// Ticks时间戳转换为DateTime
        /// </summary>
        /// <param name="timestamp">Ticks时间戳</param>
        /// <param name="timestampUnit">Ticks时间戳单位</param>
        /// <param name="dateTimeKind">时间类型,默认类型为本地时间</param>
        /// <param name="precision">时间戳精度</param>
        /// <returns>DateTime</returns>
        public DateTime TicksTimestampToDateTime(double timestamp, TimestampUnit timestampUnit, DateTimeKind dateTimeKind, uint precision)
        {
            DateTime time;
            switch (timestampUnit)
            {
                case TimestampUnit.Day:
                case TimestampUnit.Hour:
                case TimestampUnit.Minute:
                case TimestampUnit.Second:
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Day:
                            time = this._refTime.AddDays(timestamp);
                            break;
                        case TimestampUnit.Hour:
                            time = this._refTime.AddHours(timestamp);
                            break;
                        case TimestampUnit.Minute:
                            time = this._refTime.AddMinutes(timestamp);
                            break;
                        case TimestampUnit.Second:
                            time = this._refTime.AddSeconds(timestamp);
                            break;
                        default:
                            throw new NotSupportedException(timestampUnit.ToString());
                    }
                    break;
                case TimestampUnit.Millisecond:
                case TimestampUnit.Microsecond:
                case TimestampUnit.Nanosecond:
                case TimestampUnit.Picosecond:
                    timestamp = timestamp * precision;
                    double microseconds;
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Millisecond:
                            microseconds = timestamp * 1e6;
                            break;
                        case TimestampUnit.Microsecond:
                            microseconds = timestamp * 1e3;
                            break;
                        case TimestampUnit.Nanosecond:
                            microseconds = timestamp;
                            break;
                        case TimestampUnit.Picosecond:
                            microseconds = timestamp / 1000;
                            break;
                        default:
                            throw new NotImplementedException(timestampUnit.ToString());
                    }

                    long ticks = (long)(microseconds / 100);
                    time = new DateTime(ticks, this._refTime.Kind);
                    break;
                default:
                    throw new NotImplementedException(timestampUnit.ToString());
            }

            if (time.Kind != dateTimeKind)
            {
                time = TimeEx.ConvertDateTimeKind(time, dateTimeKind);
            }

            return time;
        }
        #endregion
        */
    }
}
